using System;
using System.Linq;
using CKS.EBE.API;
using CKS.EBE.Logging;
using Microsoft.SharePoint;
using System.IO;
using System.Diagnostics;
using System.Threading;
using Microsoft.SharePoint.Administration;

namespace CKS.EBE
{
	public class FeatureReceiver : SPFeatureReceiver
	{
		public override void FeatureActivated(SPFeatureReceiverProperties properties)
		{
			var oWeb = properties.Feature.Parent as SPWeb;
			if (oWeb == null)
			{
				var site = properties.Feature.Parent as SPSite;
				if (site != null)
				{
					oWeb = site.OpenWeb();
				}
			}
			if (oWeb == null)
			{
				throw new ArgumentNullException("properties", "Can't find a reference to a web!");
			}

			//var oWeb = (SPWeb)properties.Feature.Parent;
			string listTitle = Localization.GetListName(Localization.BlogList.Posts, oWeb.Language);

			if (BlogPostsListExist(oWeb, listTitle) == false)
			{
				// if there is no posts list in the web, cancel the activation
				throw new SPException(string.Format("This site does not contain a list '{0}' which stores blog posts. Cannot activate the feature", listTitle));
			}

			try
			{
				using (var site = new SPSite(oWeb.Url))
				using (var web = site.OpenWeb())
				{
					CreateThemesLibrary(web);
					CreateMediaLibrary(web);
					AddFilesToRootWeb(properties, web);
					AddWebProperties(web, properties);
					EnableThemesFeature(web);

					EnsurePagesListAndContinue(properties, web, site);

					// SharePoint 2013 specific
					if (SPFarm.Local.BuildVersion.Major == 15)
					{
						// ensure lists of type link
						EnsureList(web, Localization.BlogList.Links, "00bfea71-2062-426c-90bf-714c59600103");
						EnsureList(web, Localization.BlogList.OtherBlogs, "00bfea71-2062-426c-90bf-714c59600103");
						ModifyPostsListForSP2013(web);
					}
				}
			}
			catch (FileNotFoundException ex)
			{
				throw new SPException("The webapplication can not be found. Make sure you enter a known URL or have \"Alternate Access Mappings\" set up correctly!\r\n" + ex);
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
				throw new SPException(ex.Message, ex);
			}
		}

		private void EnsureList(SPWeb web, Localization.BlogList list, string listFeatureId)
		{
			var listRootFolderName = Localization.GetListName(list, web.Language);
			if (web.Lists.Cast<SPList>().Any(l => l.RootFolder.Name == listRootFolderName)) return;

			web.Lists.Add(listRootFolderName, string.Empty, "Lists/" + listRootFolderName, listFeatureId, (int) SPListTemplateType.Links, "100");
		}

		private static void EnsurePagesListAndContinue(SPFeatureReceiverProperties properties, SPWeb oWeb, SPSite site)
		{
			try
			{
				SPList oPostList = GetListByRootFolderName(oWeb, Localization.GetListName(Localization.BlogList.Posts, oWeb.Language));

				if (oPostList != null)
				{
					// Post list exists...probably activated via STSADM or the UI...continue as normal
					CompleteFeatureActivated(oWeb.ID, site.ID);
				}
				else
				{
					SpawnActivationThread(properties, site);
				}

			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex);

				// Post list not found...probably being activated from a site definition
				SpawnActivationThread(properties, site);
			}
		}

		private static void SpawnActivationThread(SPFeatureReceiverProperties properties, SPSite site)
		{
			// Feature has been activated, but the site has not been fully created.
			// Create a thread to wait for the site to be created...

			Debug.WriteLine("Spawning activation thread");

			var oThread = new Thread(UpdateThreadMethod);

			oThread.Start(new object[] {((SPWeb) properties.Feature.Parent).ID, properties, site.ID});
		}

		private static void UpdateThreadMethod(object properties)
		{
			for (int c = 0; c < 60; c++)
			{
				try
				{
					var arArgs = (object[]) properties;
					Thread.Sleep(1500); // Wait until the site is ready (post list exists)

					using (var oSite = new SPSite((Guid) arArgs[2]))
					using (SPWeb oWeb = oSite.OpenWeb((Guid) arArgs[0]))
					{
						SPList oPostList = Helper.GetListByFolderName(oWeb, Localization.GetListName(Localization.BlogList.Posts, oWeb.Language));

						if (oPostList != null)
						{
							try
							{
								CompleteFeatureActivated((Guid) arArgs[0], (Guid) arArgs[2]);

							}
							catch (Exception ex)
							{
								Trace.WriteLine(ex);
							}
							return;
						}
					}
				}
				catch (Exception ex)
				{
					Debug.WriteLine(ex);
				}
			}
			Trace.WriteLine("Failed to activate the EBE...Thread timeout");
		}

		private static void CompleteFeatureActivated(Guid oWebGuid, Guid oSiteGuid)
		{
			try
			{
				using (var oSite = new SPSite(oSiteGuid))
				using (SPWeb oWeb = oSite.OpenWeb(oWebGuid))
				{
					UpdatePostsList(oWeb);
					UpdateCommentsList(oWeb);
					ModifyCommentsList(oWeb);

					EnableLinkbackFeature();
					AddLinkbackFunctionality(oWeb);
				}

			}
			catch (Exception ex)
			{
				throw new Exception("Failed to complete the EBE activation: " + ex.Message, ex);
			}
			Trace.Write("Web Feature Activated");
		}

		private static void AddWebProperties(SPWeb oWeb, SPFeatureReceiverProperties properties)
		{
			try
			{
				if (properties.Feature.Properties.Count == 0) return;

				foreach (SPFeatureProperty fp in properties.Feature.Properties)
				{
					if (!fp.Name.StartsWith("_cks")) continue;

					if (oWeb.Properties.ContainsKey(fp.Name))
					{
						oWeb.Properties[fp.Name] = fp.Value;
					}
					else
					{
						oWeb.Properties.Add(fp.Name, fp.Value);
					}
				}

				oWeb.AllowUnsafeUpdates = true;
				oWeb.Properties.Update();
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
			}
		}

		public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
		{
			Debug.WriteLine("De-activating the EBE");

			var oWeb = (SPWeb) properties.Feature.Parent;
			SPList oPostList;

			try
			{
				SPWeb currentWeb = SPContext.Current.Web;
				if (BlogPostsListExist(currentWeb, Localization.GetListName(Localization.BlogList.Posts, currentWeb.Language)))
				{
					Debug.WriteLine("Getting posts list");
					oPostList = GetListByRootFolderName(currentWeb, Localization.GetListName(Localization.BlogList.Posts, oWeb.Language));

					SPField oCat = oPostList.Fields[SPBuiltInFieldId.PostCategory];
					oCat.Hidden = false;
					oCat.ShowInEditForm = true;
					oCat.ShowInNewForm = true;
					oCat.Update();

					Debug.WriteLine("Removing Event Receivers");
					string[] arType = typeof (PostEventReceiver).AssemblyQualifiedName.Split(",".ToCharArray(), 2);
					string eventReceiverAssemblyName = arType[1];
					RemoveEventReceivers(eventReceiverAssemblyName, oPostList.EventReceivers);

					oPostList.Update();
				}
				else
				{
					// the posts list does not exist
					Debug.WriteLine("The posts list does not exist!");
				}
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex);
				throw new SPException("Failed to deactivate EBE: " + ex.Message, ex);
			}

		}

		public override void FeatureInstalled(SPFeatureReceiverProperties properties)
		{

		}

		public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
		{

		}

		protected static void AddFile(SPFolder oFolder, string fileName, SPFeatureReceiverProperties properties)
		{
			try
			{
				Debug.Write("Adding..." + fileName);
				Stream oStream = null;
				try
				{
					oStream = File.OpenRead(properties.Definition.RootDirectory + "\\Pages\\" + fileName);
					oFolder.Files.Add(fileName, oStream, true);
					oStream.Close();
				}
				catch
				{
					SPSecurity.RunWithElevatedPrivileges(delegate
						{
							oStream = File.OpenRead(properties.Definition.RootDirectory + "\\Pages\\" + fileName);
							oFolder.Files.Add(fileName, oStream);
							oStream.Close();
						});
				}
				finally
				{
					if (oStream != null) oStream.Dispose();
				}
			}
			catch (Exception ex)
			{
				throw new Exception("Failed to add file from the feature...check the file permissions for the /12/TEMPLATE/FEATURES folder\n" + ex, ex);
			}
		}

		private static void EnableThemesFeature(SPWeb oWeb)
		{
			try
			{
				Debug.Write("Adding the themes feature");
				// Should check to see if it exists
				oWeb.Features.Add(BlogContext.BlogThemeFeatureGUID, true);
			}
			catch
			{
			}
		}

		private static void EnableLinkbackFeature()
		{
			try
			{
				// This is done in this feature now??

				//Trace.Write("Adding the linkback feature");
				// Should check to see if it exists
				//oWeb.Features.Add(BlogContext.BlogLinkbackFeatureGUID, true);
			}
			catch
			{
			}
		}

		private static void AddLinkbackFunctionality(SPWeb oWeb)
		{
			try
			{
				Debug.Write("Setting up linkback lists");
				LinkbackListsSetup.AddLists(oWeb);
			}
			catch
			{
			}
		}

		private static void ModifyCommentsList(SPWeb oWeb)
		{
			Debug.WriteLine("Modifying the comments list");
			SPList oComments = GetListByRootFolderName(oWeb, Localization.GetListName(Localization.BlogList.Comments, oWeb.Language));
			oComments.EnableModeration = true;

			try
			{
				oComments.Fields.GetField("CommentUrl");
			}
			catch
			{
				oComments.Fields.Add("CommentUrl", SPFieldType.Text, false);
			}

			oComments.Update();
		}

		private static void CreateThemesLibrary(SPWeb oWeb)
		{
			Debug.WriteLine("Creating the Themes library");

			SPList oThemesList = null;
			try
			{
				oThemesList = oWeb.Lists[Localization.GetListName(Localization.BlogList.Themes, oWeb.Language)];
			}
			catch
			{
			}

			if (oThemesList == null)
			{
				// To do : Create with common path ?
				Guid id = oWeb.Lists.Add("Themes", Localization.GetResource("ListDescription_Themes", oWeb.Language), SPListTemplateType.DocumentLibrary);
				oThemesList = oWeb.Lists["Themes"];
				oThemesList.Title = Localization.GetListName(Localization.BlogList.Themes, oWeb.Language);
				oThemesList.Update();
			}
		}

		private static void CreateMediaLibrary(SPWeb oWeb)
		{
			Debug.WriteLine("Creating the media library");

			SPList oMedia = null;
			try
			{
				oMedia = oWeb.Lists[Localization.GetListName(Localization.BlogList.Media, oWeb.Language)];
			}
			catch
			{
			}

			if (oMedia == null)
			{
				Guid id = oWeb.Lists.Add("Media", Localization.GetResource("ListDescription_Media", oWeb.Language), SPListTemplateType.DocumentLibrary);
				oMedia = oWeb.Lists["Media"];
				oMedia.Title = Localization.GetListName(Localization.BlogList.Media, oWeb.Language);
				oMedia.Update();
			}
		}

		private static void UpdatePostsList(SPWeb oWeb)
		{
			Debug.WriteLine("Modifying the posts list");

			SPList oPostList = null;
			SPList oCatList = null;

			try
			{
				oPostList = GetListByRootFolderName(oWeb, Localization.GetListName(Localization.BlogList.Posts, oWeb.Language));
				oCatList = GetListByRootFolderName(oWeb, Localization.GetListName(Localization.BlogList.Categories, oWeb.Language));
			}
			catch (Exception ex)
			{
				Trace.WriteLine("Failed to get the Posts or Categories list");
				Logger.Error(ex, "FeatureReceiver.UpdatePostsList");
			}

			if (oPostList == null) return;

			SetupPostItemEventReceivers(oPostList.EventReceivers);

			SPFieldCollection oPostListFields = oPostList.Fields;

			#region  BlogTitleForUrl

			try
			{
				oPostListFields.GetFieldByInternalName("BlogTitleForUrl");
			}
			catch
			{
				string sIntNameUrlTitle = oPostListFields.Add("BlogTitleForUrl", SPFieldType.Text, false);
				SPField oIntNameUrlTitle = oPostListFields[sIntNameUrlTitle];
				oIntNameUrlTitle.Title = Localization.GetResource("FieldTitle_BlogTitleForUrl", oWeb.Language);
				oIntNameUrlTitle.Description = Localization.GetResource("FieldDescription_BlogTitleForUrl", oWeb.Language);
				oIntNameUrlTitle.Hidden = true;
				oIntNameUrlTitle.ShowInEditForm = false;
				oIntNameUrlTitle.Indexed = true;
				try
				{
					oIntNameUrlTitle.Update();
				}
				catch (Exception ex)
				{
					Logger.Error(ex, "FeatureReceiber.UpdatePostsList.BlogTitleForUrl");
				}
			}

			#endregion

			#region CategoriesAsText

			try
			{
				oPostListFields.GetFieldByInternalName("CategoriesAsText");
			}
			catch
			{
				Trace.Write("Creating CategoriesAsText");
				// Field required for compatibility with RSS
				string sCatText = oPostListFields.Add("CategoriesAsText", SPFieldType.Note, false);
				SPField oCatText = oPostListFields[sCatText];
				oCatText.Title = Localization.GetResource("FieldTitle_CategoriesAsText", oWeb.Language);
				oCatText.Hidden = true;
				oCatText.ShowInEditForm = false;
				oCatText.ShowInNewForm = false;
				oCatText.Update();
			}

			#endregion

			#region Hide PostCategory

			try
			{
				// Hide the original category field
				SPField oCat = oPostListFields[SPBuiltInFieldId.PostCategory];
				oCat.Hidden = true;
				oCat.ShowInEditForm = false;
				oCat.ShowInNewForm = false;
				oCat.Update();
			}
			catch (Exception ex)
			{
				Logger.Error(ex, "FeatureReceiber.UpdatePostsList.PostCategory");
			}

			#endregion

			#region Clean up old columns

			try
			{
				// Delete fields that may have been left over from previous releases:
				SPField oCats = oPostListFields.GetFieldByInternalName("Categories");
				if (!(oCats is SPFieldLookup))
				{
					Trace.Write("Deleting old Cats column");
					oCats.Delete();
					CreateCategoriesLookupField(oPostList, oCatList, oWeb.Language);
				}
				else
				{
					Trace.Write("Showing the Categories column");
					oCats.Hidden = false;
					oCats.ShowInEditForm = true;
					oCats.ShowInNewForm = true;

					oCats.Update();
				}
			}
			catch
			{
				CreateCategoriesLookupField(oPostList, oCatList, oWeb.Language);
			}

			#endregion

			Trace.Write("Updating post list");

			oPostList.Update();

			// Ensure the Url re-writing will work
			SPListItemCollection existingPosts = oPostList.Items;
			foreach (SPListItem oPost in existingPosts)
			{
				PostEventReceiver.UpdateTitleForUrl(oPost);
				// Update categories if needed
				UpdateCategories(oPost);
			}
		}

		private static void UpdateCommentsList(SPWeb oWeb)
		{
			Debug.WriteLine("Modifying the posts list");

			try
			{
				//SPList oCommentslist = oWeb.Lists[Localization.GetListName(Localization.BlogList.Comments, oWeb.Language)];
				SPList oCommentslist = GetListByRootFolderName(oWeb, Localization.GetListName(Localization.BlogList.Comments, oWeb.Language));
				try
				{
					oCommentslist.Fields.GetFieldByInternalName("EmailAddress");
				}
				catch (Exception)
				{
					Trace.WriteLine("Email field does not exist in comments list. Creating it");
					oCommentslist.Fields.Add("EmailAddress", SPFieldType.Text, false);
				}
			}
			catch (Exception ex)
			{
				Trace.WriteLine("Failed to get the Comments list");
				Trace.WriteLine(ex);
			}
		}

		private static void UpdateCategories(SPListItem oPostItem)
		{
			// Update categories if needed
			var fvCats = (SPFieldLookupValueCollection) oPostItem["Categories"];
			var fvCat = new SPFieldLookupValue(oPostItem["PostCategory"] as string);

			if (fvCat.LookupValue != null && fvCats.Count == 0)
			{
				fvCats.Add(fvCat);
				oPostItem["Categories"] = fvCats;
				oPostItem.SystemUpdate();
			}
		}

		private static void AddFilesToRootWeb(SPFeatureReceiverProperties properties, SPWeb oWeb)
		{
			SPFolder oRootFolder = oWeb.RootFolder;

			AddFile(oRootFolder, "Home.aspx", properties);
			AddFile(oRootFolder, "Post.aspx", properties);
			AddFile(oRootFolder, "Category.aspx", properties);
			AddFile(oRootFolder, "Contact.aspx", properties);
			AddFile(oRootFolder, "Month.aspx", properties);
			// TODO : add "about me" ?
		}

		private static void SetupPostItemEventReceivers(SPEventReceiverDefinitionCollection eventReceivers)
		{
			try
			{
				Trace.Write("Enabling EBE Events");

				string[] arType = typeof (PostEventReceiver).AssemblyQualifiedName.Split(",".ToCharArray(), 2);
				string eventReceiverAssemblyName = arType[1];
				string eventReceiverClassName = arType[0];

				RemoveEventReceivers(eventReceiverAssemblyName, eventReceivers);

				// One line for each event type:
				eventReceivers.Add(SPEventReceiverType.ItemAdded, eventReceiverAssemblyName, eventReceiverClassName);
				eventReceivers.Add(SPEventReceiverType.ItemUpdated, eventReceiverAssemblyName, eventReceiverClassName);
				eventReceivers.Add(SPEventReceiverType.ItemDeleted, eventReceiverAssemblyName, eventReceiverClassName);

				Trace.Write("EBE events enabled");
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex);
				// During development the DLL may not be in the GAC
				// and so registering events will fail
			}
		}

		private static void RemoveEventReceivers(string eventReceiverAssemblyName, SPEventReceiverDefinitionCollection eventReceivers)
		{
			Debug.WriteLine("Removing event recieves");

			SPEventReceiverDefinition currentReceiver;
			for (int i = eventReceivers.Count - 1; i >= 0; i--)
			{
				currentReceiver = eventReceivers[i];
				if (currentReceiver.Assembly == eventReceiverAssemblyName)
				{
					currentReceiver.Delete();
				}
			}
		}

		private static void CreateCategoriesLookupField(SPList oPostList, SPList oCatList, uint language)
		{
			Trace.Write("Create EBE Categories LookupField");

			string catFieldName = oPostList.Fields.AddLookup("Categories", oCatList.ID, false);
			var oField = (SPFieldLookup) oPostList.Fields.GetFieldByInternalName(catFieldName);
			oField.Title = Localization.GetResource("FieldTitle_Categories", language);
			oField.AllowMultipleValues = true;
			oField.LookupField = "Title";
			//oField.Hidden = false;
			//oField.ShowInEditForm = false;
			//oField.ShowInNewForm = false;
			oField.Update();
		}

		/// <summary>
		/// check if a list with the title exists in the web, and if it is a posts list
		/// </summary>
		/// <param name="web"></param>
		/// <param name="listTitle"></param>
		/// <returns></returns>
		private static bool BlogPostsListExist(SPWeb web, string listTitle)
		{
			foreach (SPList list in web.Lists)
			{
				if (list.RootFolder.Name == listTitle && list.BaseTemplate == SPListTemplateType.Posts)
				{
					return true;
				}
			}
			return false;
		}

		private static SPList GetListByRootFolderName(SPWeb web, string rootFolderName)
		{
			foreach (SPList list in web.Lists)
			{
				if (list.RootFolder.Name == rootFolderName)
				{
					return list;
				}
			}
			return null;
		}

		private void ModifyPostsListForSP2013(SPWeb web)
		{
			using (var blogWeb = web.Site.OpenWeb(web.ID))
			{
				try
				{
					blogWeb.AllowUnsafeUpdates = true;
					SPList postList = null;
					SPList categoryList = null;

					try
					{
						postList = GetListByRootFolderName(blogWeb, Localization.GetListName(Localization.BlogList.Posts, blogWeb.Language));
						categoryList = GetListByRootFolderName(blogWeb, Localization.GetListName(Localization.BlogList.Categories, blogWeb.Language));
					}
					catch (Exception ex)
					{
						Trace.WriteLine("Failed to get the Posts or Categories list");
						Logger.Error(ex, "FeatureReceiver.ModifyPostsListForSP2013");
					}

					if (postList == null) return;
					var postListFields = postList.Fields;

					// Show the original category field
					// it can be used with SharePoint 2013, because it is a multivalue lookup field
					SPField postCategoryField = postListFields[SPBuiltInFieldId.PostCategory];
					postCategoryField.Hidden = false;
					postCategoryField.ShowInEditForm = true;
					postCategoryField.ShowInNewForm = true;
					postCategoryField.Update();
					// hide the ebe category field
					var ebePostCategoryField = (SPFieldLookup) postList.Fields.GetFieldByInternalName("Categories");
					ebePostCategoryField.Hidden = true;
					ebePostCategoryField.Update();

					SPEventManagerWrapper.DisableEventFiring();
					SPListItemCollection existingPosts = postList.Items;
					foreach (SPListItem post in existingPosts)
					{
						// Update categories if needed
						var oldCategories = post["Categories"] as SPFieldLookupValueCollection;
						var newCategories = post["PostCategory"] as SPFieldLookupValueCollection;
						if (oldCategories != null && newCategories != null &&
						    oldCategories.ToString() != newCategories.ToString())
						{
							post["PostCategory"] = post["Categories"];
							post.SystemUpdate(false);
						}
					}
				}
				catch (Exception ex)
				{
					Logger.Error(ex, "FeatureReceiver.ModifyPostsListForSP2013");
				}
				finally
				{
					SPEventManagerWrapper.EnableEventFiring();
				}
			}
		}
	}
}